import logging
import os
import subprocess
from datetime import datetime
from json import loads, dumps

from PyQt5.QtCore import Qt, QDate
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QWidget, QGridLayout, QPushButton, QTableWidget, \
    QHeaderView, QCheckBox, QDateEdit, \
    QHBoxLayout, QMessageBox

from school_ringer_modules.config import config_path
from school_ringer_modules.system import run_command


def qdate_from_qdateedit_str(date_string: str) -> QDate:
    """
    Преобразование из текста в QDate
    @param date_string: str
    @return: QDate
    """
    day, month, year = map(int, date_string.split('.'))
    return QDate(year, month, day)


def is_holiday_today() -> bool:
    """
    Определяет, попадает ли сегодняшний день в каникулы
    @return: True, если попадает, иначе False
    """
    try:
        with open(f'{config_path}/holidays.json', 'r', encoding='utf-8') as file:
            holidays_from_file = loads(file.read())
        today = QDate.currentDate()
        for holiday in holidays_from_file:
            if holidays_from_file[holiday]['enabled'] is True:
                if qdate_from_qdateedit_str(holidays_from_file[holiday]['start']) <= today <= \
                        qdate_from_qdateedit_str(holidays_from_file[holiday]['end']):
                    return True
        return False

    except Exception as e:
        logging.info(f'Не удалось определить даты каникул, {e}')
        return False


class CalendarEditor(QWidget):
    def __init__(self) -> None:
        """
        Конструктор, создающий экземпляр класса
        """
        super().__init__()
        self.initUI()

    def load_data(self) -> None:
        """
        Заполнение таблицы UI данными из json-файла
        """
        try:
            with open(f'{config_path}/holidays.json', 'r', encoding='utf-8') as file:
                holidays_from_file = loads(file.read())
            for holiday in holidays_from_file:
                self.checkboxes[int(holiday)].setChecked(holidays_from_file[holiday]['enabled'])
                if self.checkboxes[int(holiday)].isChecked():
                    row = int(holiday)
                    self.calendar_table.cellWidget(row, 1).setEnabled(True)
                    self.calendar_table.cellWidget(row, 2).setEnabled(True)
                self.calendar_table.cellWidget(int(holiday), 1).setDate(
                    qdate_from_qdateedit_str(holidays_from_file[holiday]['start'])
                )
                self.calendar_table.cellWidget(int(holiday), 2).setDate(
                    qdate_from_qdateedit_str(holidays_from_file[holiday]['end'])
                )
        except Exception:
            logging.info('Файл с каникулами не найден или некорректен')

    def save_holidays(self) -> None:
        """
        Сохраняет коникулы в файл конфига с проверкой корректности заполнения
        @return: None
        """
        output = dict()
        for row in range(self.holidays_cnt):
            output[str(row)] = dict()
            output[str(row)]['enabled'] = self.checkboxes[row].isChecked()
            output[str(row)]['start'] = self.calendar_table.cellWidget(row, 1).text()
            output[str(row)]['end'] = self.calendar_table.cellWidget(row, 2).text()

            if (self.checkboxes[row].isChecked() and
                    datetime.strptime(output[str(row)]['start'], '%d.%m.%Y') >
                    datetime.strptime(output[str(row)]['end'], '%d.%m.%Y')):
                dlg = QMessageBox()
                dlg.setWindowTitle('Внимание!')
                dlg.setText(f'Невозможно сохранить каникулы: в строке {row + 1} каникулы начинаются позже, чем '
                            f'заканчиваются!')
                dlg.exec()
                logging.info(f"Каникулы не сохранены: {output[str(row)]['start']} > {output[str(row)]['end']}")
                return

        tempfile = run_command('mktemp').strip()
        with open(tempfile, 'w', encoding='utf-8') as file:
            file.write(dumps(output, indent=4, ensure_ascii=False))
        if subprocess.run(f'pkexec sh -c '
                          f'"mv {tempfile} {config_path}/holidays.json && '
                          f'chmod 644 {config_path}/holidays.json"', shell=True).returncode == 0:
            logging.info(f'Каникулы перенесены в файл {config_path}/holidays.json')
        else:
            logging.info('При сохранении каникул возникла ошибка. Проверьте пароль.')

    def cell_was_clicked(self, row: int, column: int) -> None:
        """
        Меняет доступность строки для редактирования дат каникул при смене состояния флажка в первом столбце
        @param row: номер строки
        @param column: номер столбца
        """
        if column == 0:
            self.checkboxes[row].nextCheckState()
            if self.checkboxes[row].isChecked():
                self.calendar_table.cellWidget(row, 1).setEnabled(True)
                self.calendar_table.cellWidget(row, 2).setEnabled(True)
            else:
                self.calendar_table.cellWidget(row, 1).setDisabled(True)
                self.calendar_table.cellWidget(row, 2).setDisabled(True)

    def checkbox_was_switched(self) -> None:
        """
        Меняет доступность строки для редактирования дат каникул при смене состояния флажка в первом столбце
        """
        row = self.checkboxes.index(self.sender())
        if self.checkboxes[row].isChecked():
            self.calendar_table.cellWidget(row, 1).setEnabled(True)
            self.calendar_table.cellWidget(row, 2).setEnabled(True)
        else:
            self.calendar_table.cellWidget(row, 1).setDisabled(True)
            self.calendar_table.cellWidget(row, 2).setDisabled(True)

    def closeEvent(self, a0: QCloseEvent) -> None:
        """
        Сигнал закрытия окна для обновления каникул
        @param a0: событие, которое вызвало закрытие окна
        """
        output = dict()
        fail_saving = False
        for row in range(self.holidays_cnt):
            output[str(row)] = dict()
            output[str(row)]['enabled'] = self.checkboxes[row].isChecked()
            output[str(row)]['start'] = self.calendar_table.cellWidget(row, 1).text()
            output[str(row)]['end'] = self.calendar_table.cellWidget(row, 2).text()
            if (self.checkboxes[row].isChecked() and
                    datetime.strptime(output[str(row)]['start'], '%d.%m.%Y') >
                    datetime.strptime(output[str(row)]['end'], '%d.%m.%Y')):
                if not fail_saving:
                    error_row = row
                fail_saving = True
        tempfile = run_command('mktemp').strip()
        with open(tempfile, 'w', encoding='utf-8') as file:
            file.write(dumps(output, indent=4, ensure_ascii=False))
        diffs = run_command(f'diff {tempfile} {config_path}/holidays.json').strip()
        if diffs == '':
            self.close()
        else:
            ret = QMessageBox.question(self, 'Внимание!', f"Сохранить изменения?",
                                       QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)

            if ret == QMessageBox.Cancel:
                a0.ignore()
            elif ret == QMessageBox.No:
                self.close()
                self.parent.holidays_editing_window = None
            elif ret == QMessageBox.Yes:
                if fail_saving:
                    dlg = QMessageBox()
                    dlg.setWindowTitle('Внимание!')
                    dlg.setText(f'Невозможно сохранить каникулы: в строке {error_row + 1} каникулы начинаются позже, '
                                f'чем заканчиваются!')
                    dlg.exec()
                    logging.info(f"Каникулы не сохранены: "
                                 f"{output[str(error_row)]['start']} > {output[str(error_row)]['end']}")
                    a0.ignore()
                else:
                    logging.info(
                        f'Сохранение расписаний в файл, обновление списка активных расписаний, '
                        'закрытие окна редактирования расписаний...')
                    self.save_holidays()
                    self.close()

    def initUI(self) -> None:
        """
        Инициализация графического интерфейса
        """
        grid = QGridLayout()
        self.setLayout(grid)

        self.calendar_table = QTableWidget()
        self.holidays_cnt = 4
        self.calendar_table.setColumnCount(3)
        self.calendar_table.setRowCount(self.holidays_cnt)
        calendar_table_headers = ['Включить', 'Дата начала', 'Дата окончания']
        self.calendar_table.setHorizontalHeaderLabels(calendar_table_headers)
        self.calendar_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.calendar_table.setColumnWidth(0, 238)
        self.calendar_table.cellClicked.connect(self.cell_was_clicked)

        self.checkboxes = []

        for row in range(self.holidays_cnt):
            cell_widget = QWidget()
            cell_checkbox = QCheckBox()

            cell_checkbox.clicked.connect(self.checkbox_was_switched)
            self.checkboxes.append(cell_checkbox)
            cell_layout = QHBoxLayout(cell_widget)
            cell_layout.addWidget(cell_checkbox)
            cell_layout.setAlignment(Qt.AlignCenter)
            cell_layout.setContentsMargins(0, 0, 0, 0)
            cell_widget.setLayout(cell_layout)

            self.calendar_table.setCellWidget(row, 0, cell_widget)
            start_holidays = QDateEdit(calendarPopup=True)
            end_holidays = QDateEdit(calendarPopup=True)
            start_holidays.setDate(QDate.currentDate())
            end_holidays.setDate(QDate.currentDate())
            self.calendar_table.setCellWidget(row, 1, start_holidays)
            self.calendar_table.setCellWidget(row, 2, end_holidays)
            self.calendar_table.cellWidget(row, 1).setDisabled(True)
            self.calendar_table.cellWidget(row, 2).setDisabled(True)

        self.load_data()

        if not os.path.isfile(f'{config_path}/holidays.json'):
            self.save_holidays()

        grid.addWidget(self.calendar_table, 0, 0, 1, 2)
        self.save_button = QPushButton('Сохранить')
        self.save_button.clicked.connect(self.save_holidays)
        grid.addWidget(self.save_button, 1, 0)

        self.cancel_button = QPushButton('Отмена')
        self.cancel_button.clicked.connect(lambda: self.close())
        grid.addWidget(self.cancel_button, 1, 1)

        self.setGeometry(300, 200, 600, 200)
        self.setWindowTitle('Редактирование каникул')
        self.show()
